Skip to content

feat(cli): auto-dispose InstanceContext after effectCmd handlers#25481

Merged
kitlangton merged 1 commit intodevfrom
kit/cli-effect-cmd-auto-dispose
May 2, 2026
Merged

feat(cli): auto-dispose InstanceContext after effectCmd handlers#25481
kitlangton merged 1 commit intodevfrom
kit/cli-effect-cmd-auto-dispose

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

Wrap the user handler in `Effect.ensuring(store.dispose(ctx))` inside the `effectCmd` factory so per-command disposal is automatic — runs disposers and emits `server.instance.disposed` on every Exit (success / typed failure / defect / interruption). Matches the legacy `bootstrap()` finally semantics without per-handler boilerplate.

Two parallel subagent reviews of the debug-conversion PR (#25479) flagged the 14× repeated `const ctx = yield* InstanceRef; if (!ctx) return; const store = yield* InstanceStore.Service; return yield* body.pipe(Effect.ensuring(store.dispose(ctx)))` pattern as a footgun. This PR moves the responsibility into `effectCmd` itself.

Behavioral notes

  • Auto-dispose fires for every `effectCmd` invocation (didn't before).
  • For commands previously converted that DIDN'T explicitly dispose (`models.ts`, `pr.ts`, `plug.ts`): they now dispose where they didn't. This is strictly safer (cleanup runs; emits IPC event the legacy `Instance.provide` would not have).
  • For commands that DO explicitly dispose (`import.ts`, `export.ts`, `stats.ts`, all of `debug/*`): the explicit `store.dispose(ctx)` becomes a no-op the second time around (`disposeEntry` checks `cache.get(directory) !== entry` and returns false). Safe but redundant — those calls can be cleaned up in a follow-up.

Followup (not in this PR)

Drop the now-redundant explicit `Effect.ensuring(store.dispose(ctx))` from `import.ts`, `export.ts`, `stats.ts`, and `debug/*.ts` once those PRs land. Keeps things tidy without breaking semantics in the meantime.

Test plan

  • `bun run typecheck`
  • `bun run test test/cli/` — 137 pass
  • Smoke: `models` and `models --refresh` (existing converted commands) still work end-to-end

const directory = opts.directory?.(args) ?? process.cwd()
await AppRuntime.runPromise(InstanceStore.Service.use((s) => s.provide({ directory }, opts.handler(args))))
await AppRuntime.runPromise(
InstanceStore.Service.use((store) =>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

@@ -2,6 +2,7 @@ import type { Argv } from "yargs"
import { Effect, Schema } from "effect"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another com

const directory = opts.directory?.(args) ?? process.cwd()
await AppRuntime.runPromise(InstanceStore.Service.use((s) => s.provide({ directory }, opts.handler(args))))
await AppRuntime.runPromise(
InstanceStore.Service.use((store) =>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hello

const directory = opts.directory?.(args) ?? process.cwd()
await AppRuntime.runPromise(InstanceStore.Service.use((s) => s.provide({ directory }, opts.handler(args))))
await AppRuntime.runPromise(
InstanceStore.Service.use((store) =>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hel

Wrap the user handler in Effect.ensuring(store.dispose(ctx)) inside the
effectCmd factory so per-command disposal is automatic — runs disposers
+ emits server.instance.disposed on every Exit (success / typed failure
/ defect / interruption). Matches the legacy bootstrap() finally without
14× hand-rolled boilerplate at the call sites.

Idempotent: existing commands that still call store.dispose(ctx) explicitly
inside their handler bodies will see the second auto-dispose call become a
no-op (cache entry already removed; disposeContext doesn't re-fire).
@kitlangton kitlangton force-pushed the kit/cli-effect-cmd-auto-dispose branch from dc7cff5 to 00bee6c Compare May 2, 2026 23:40
@kitlangton kitlangton merged commit 85bb900 into dev May 2, 2026
10 checks passed
@kitlangton kitlangton deleted the kit/cli-effect-cmd-auto-dispose branch May 2, 2026 23:54
aprogramq pushed a commit to aprogramq/opencode that referenced this pull request May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant